#pragma once

#include <iostream>
#include <string>
#include <stdlib.h>

#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>

#include "../comm/utils.hpp"
#include "../comm/log.hpp"

using std::cout;
using std::cin;
using std::endl;
using std::string;

namespace ns_compiler
{
    using namespace ns_utils;
    using namespace ns_log;

    class Compiler
    {
    public:
        /**
         * @brief 编译源文件，生成可执行程序
         *
         * @param[in] file 待编译待源文件，不包含后缀名
         *
         * @return true 或 false
         */
        static bool compile(const string& file)
        {
            pid_t pid = fork();
            if(-1 == pid)
            {
                LOG(ERROR) << "创建子进程失败，无法进行下一步编译任务" << endl;

                return false;
            }
            else if(0 == pid) // 子进程
            {
                umask(0); // 文件权限 = mode&~umask ，管理员umask默认为0022,普通用户umask默认为0002
                int new_stderr = open(PathUtil::compile_err_file(file).c_str(),O_CREAT | O_WRONLY,0644);

                if(-1 == new_stderr)
                {
                    LOG(WARING) << "创建编译临时文件失败，无法进行下一步编译任务" << endl;

                    exit(1);
                }
                 // 标准错误重定向，当编译失败时从文件中获取错误信息
                if(-1 == dup2(new_stderr,2))
                {
                    LOG(WARING) << "标准错误输出重定向失败，无法进行下一步编译任务" << endl;

                    exit(2);
                }
                /* 程序替换并不会影响进程的文件描述符表 -D COMPILER_ONLINE,在test_code.cpp
                 * 中引入basic_cpp，以免报错，将用户提交代码与test_code.cpp合并时需要定义宏COMPILER_ONLINE
                 * 去掉#include basic_cpp，e以免编译时报错
                 * */
                execlp("g++","g++","-o",PathUtil::exec_file(file).c_str(),\
                       PathUtil::src_file(file).c_str(),"-std=c++11","-D","COMPILER_ONLINE",nullptr);

                LOG(ERROR) << "程序替换失败,无法进行下一步编译任务" << endl;

                exit(3);
            }
            else // 父进程
            {
                /* 阻塞等待执行编译任务的子进程,通过是否了生成可执行程序判断任务成功与否 */
                waitpid(pid,nullptr,0);
                if(FileUtil::is_file_exists(PathUtil::exec_file(file)))
                {
                    LOG(INFO) << PathUtil::src_file(file) << " 编译成功" << endl;

                    return true;
                }
            }

            LOG(ERROR) << PathUtil::src_file(file) << " 编译失败，没有形成可执行程序" << endl;

            return false;
        }
    };
}
